package com.jotta.client.communicate;

import java.io.*;
import java.util.ArrayList;
import java.util.Calendar;

import com.jotta.client.exception.DataParseException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.jotta.client.R;
import com.jotta.client.data.FileInfo;
import com.jotta.client.data.ItemInfo;
import com.jotta.client.data.PictureInfo;
import com.jotta.client.data.TypeOfFile;
import com.jotta.client.dataaccess.DataAccess;
import com.jotta.client.exception.NetworkException;
import com.jotta.client.exception.ServerException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

public class ServerCommunicator {

    public static String TAG = "JOTTA_ServerCommunicator";

    private static final int MAX_BUFFER_SIZE = 1024;
    private static ServerCommunicator instance;
    private String connectingProgressBarTitle = Configuration.getRes().getString(R.string.connecting_server_progress_bar_title);
    private String progressBarTitle = Configuration.getRes().getString(R.string.progress_bar_title);

    private Handler progressBarHandlerRef = null;


    public ServerCommunicator() {


    }

    public static ServerCommunicator getInstance() {
        if (instance == null)
            instance = new ServerCommunicator();
        return instance;
    }


    public static ServerCommunicator getInstance(Handler prgBh) {
        if (instance == null)
            instance = new ServerCommunicator();

        instance.progressBarHandlerRef = prgBh;
        return instance;
    }

    private PathManager pathManager = new PathManager();
    private DefaultHttpClient httpclient;
    private ArrayList<ItemInfo> folderInfo;

    public ArrayList<ItemInfo> getFolderInfo() {
        return folderInfo;
    }

    public String getCurrentFolder() {
        return pathManager.getCurrentFolderName();
    }

    public String getCurrentPath() {
        return pathManager.getCurrentPath();
    }

    public JottaPath getCurrentJottaPath() {
        return pathManager.getCurrentJottaPath();
    }

    public void shutdown() {
        // When HttpClient instance is no longer needed,
        // shut down the connection manager to ensure
        // immediate deallocation of all system resources
        if (httpclient != null)
            httpclient.getConnectionManager().shutdown();
    }


    public void logIn(String username, String password)
            throws NetworkException, ServerException, DataParseException {

        httpclient = new DefaultHttpClient();
        httpclient.getCredentialsProvider().setCredentials(
                new AuthScope(Configuration.SERVER_HOST, AuthScope.ANY_PORT),
                new UsernamePasswordCredentials(username, password));

        pathManager.resetJottaPath(username);
        updateCurrentFolderInfo();

    }

    public void restart(String username, String password, String latestPath)
            throws NetworkException, ServerException, DataParseException {

        httpclient = new DefaultHttpClient();
        httpclient.getCredentialsProvider().setCredentials(
                new AuthScope(Configuration.SERVER_HOST, AuthScope.ANY_PORT),
                new UsernamePasswordCredentials(username, password));

        pathManager.reconnectSever(username, latestPath);
        updateCurrentFolderInfo();
    }


    private void updateCurrentFolderInfo()
            throws NetworkException, ServerException, DataParseException {


        try {
            folderInfo = Parser.getInstance().parseXmlData(callServerUrl(), getCurrentJottaPath());

            sendProgressBarHandlerMessage(100, progressBarTitle, -1);

        } catch (IOException e) {
            Log.w(TAG, "IOException:" + e);
            e.printStackTrace();
            throw new DataParseException("Could not parse data:" + e.getMessage());


        } catch (SAXException e) {
            Log.w(TAG, "SAXException:" + e);
            e.printStackTrace();
            throw new DataParseException("Could not parse data:" + e.getMessage());
        }


    }


    private Document callServerUrl()
            throws NetworkException, ServerException, SAXException, DataParseException {
        //Log.i(TAG, "newPath:" + newPath);
        try {
            HttpGet httpget = new HttpGet(getCurrentJottaPath().getFullPathEncoded());
            HttpResponse response = httpclient.execute(httpget);
            sendProgressBarHandlerMessage(30, progressBarTitle, -1);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200 || statusCode == 201) {
                sendProgressBarHandlerMessage(70, progressBarTitle, -1);
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    return Parser.getInstance().parseInputStream(entity.getContent());
                }else {
                    throw new DataParseException("No data in server response");
                }

            } else {
                String errorMessage = response.getStatusLine().getReasonPhrase();
                throw new ServerException(errorMessage, statusCode);
            }
        } catch (IOException e) {
            //Log.e(ListItem.TAG, "Login failed", e);
            throw new NetworkException(e.getMessage());
        }
    }


    public void browseFolder(String folderName, boolean isArchiveFolder)
            throws NetworkException, ServerException, DataParseException {
        //Log.w(TAG, "folderName:" + folderName);
        pathManager.goForward(folderName, isArchiveFolder);
        updateCurrentFolderInfo();
    }


    public ArrayList<PictureInfo> getPictureInfos() {
        if (folderInfo != null) {
            ArrayList<PictureInfo> pictureInfos = new ArrayList<PictureInfo>();
            for (ItemInfo itemInfo : folderInfo) {
                if (itemInfo instanceof FileInfo) {
                    FileInfo fileInfo = (FileInfo) itemInfo;
                    if (fileInfo.getTypeOfFile() == TypeOfFile.PICTURE) {
                        pictureInfos.add(new PictureInfo(fileInfo));
                    }
                }
            }
            return pictureInfos;
        }
        return null;
    }

    public ArrayList<PictureInfo> getPictureInfos(String username,
                                                  String password,
                                                  String browsePath)
            throws NetworkException, ServerException, IOException, SAXException, DataParseException {

        httpclient = new DefaultHttpClient();
        httpclient.getCredentialsProvider().setCredentials(
                new AuthScope(Configuration.SERVER_HOST, AuthScope.ANY_PORT),
                new UsernamePasswordCredentials(username, password));
        pathManager.reconnectSever(username, browsePath);

        ArrayList<PictureInfo> result = Parser.getInstance().getPictureInfos(callServerUrl(), browsePath);
        sendProgressBarHandlerMessage(100, progressBarTitle, -1);

        return result;
    }


    public void goHome() throws NetworkException, ServerException, DataParseException {
        pathManager.goHome();
        updateCurrentFolderInfo();
    }

    public void goBack() throws NetworkException, ServerException, DataParseException {
        pathManager.goBack();
        updateCurrentFolderInfo();
    }


    public String downloadFile(String downloadPath, String severPath, long fileSize)
            throws NetworkException, ServerException, IOException {
        File localFilePath = DataAccess.getInstance().createLocalFile(severPath);
        try {
            // write into file
            HttpGet httpget = new HttpGet(downloadPath);
            HttpResponse response = httpclient.execute(httpget);
            sendProgressBarHandlerMessage(0, connectingProgressBarTitle, -1);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200 || statusCode == 201) {

                int downloadedSize = 0;
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    InputStream stream = entity.getContent();
                    sendProgressBarHandlerMessage(0, connectingProgressBarTitle, -1);
                    byte buf[] = new byte[MAX_BUFFER_SIZE];
                    int numBytesRead = 0;

                    BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(localFilePath), 8000);

                    long startTime = Calendar.getInstance().getTimeInMillis();

                    do {

                        if (Configuration.getStateOfClient() == StateOfClient.DOWNLOADING
                                || Configuration.getStateOfClient() == StateOfClient.DOWNLOADING_PICTURE) {

                            numBytesRead = stream.read(buf);

                            if (numBytesRead > 0) {
                                fos.write(buf, 0, numBytesRead);
                                downloadedSize += numBytesRead;

                                long currentTime = Calendar.getInstance()
                                        .getTimeInMillis();
                                if ((currentTime - startTime) % 300 == 0) {
                                    float ratio = 100;

                                    if (fileSize != 0)
                                        ratio = (downloadedSize * 100) / fileSize;

                                    long speed = 100;
                                    if (currentTime - startTime != 0)
                                        speed = Math.abs(((downloadedSize * 1000) / 1024) / (currentTime - startTime));

                                    sendProgressBarHandlerMessage((int) (ratio), connectingProgressBarTitle, speed);
                                }

                            }
                        } else {
                            fos.flush();
                            fos.close();
                            stream.close();
                            if (localFilePath.exists())
                                localFilePath.delete();

                            throw new NetworkException(Configuration.getRes().getString(R.string.unexpected_stop_downloading));
                        }
                    } while (numBytesRead > -1);

                    fos.flush();
                    fos.close();
                    stream.close();
                    sendProgressBarHandlerMessage(100, progressBarTitle, -1);
                    return localFilePath.getAbsolutePath();
                }
                throw new NetworkException("No file to download");
            } else {
                String errorMessage = response.getStatusLine().getReasonPhrase();
                throw new ServerException(errorMessage, statusCode);
            }

        } catch (IOException e) {
            if (localFilePath != null && localFilePath.exists()) {
                localFilePath.delete();
            }
            String error = Log.getStackTraceString(e);
            throw new NetworkException(e.getMessage() + error);
        }

    }

    private void sendProgressBarHandlerMessage(int percent, String message, long speed) {
        if (getProgressBarHandlerRef() != null) {

            Bundle bundle = new Bundle();
            Message msg = new Message();
            if (speed != -1) {
                message += " " + speed + "kB/s";
            }
            bundle.putInt(Configuration.PROGRESS_BAR_PERCENT, percent);
            bundle.putString(Configuration.DOWNLOAD_HEADER, message);
            msg.setData(bundle);
            getProgressBarHandlerRef().sendMessage(msg);
        }
    }

    public Handler getProgressBarHandlerRef() {
        return progressBarHandlerRef;
    }

    public void setProgressBarHandlerRef(Handler progressBarHandlerRef) {
        this.progressBarHandlerRef = progressBarHandlerRef;
    }

    protected void finalize() throws Throwable {
        shutdown();
        super.finalize();
    }
}
